package com.mtr.databoard.domain.screens;

import com.mtr.databoard.common.linqlist.LinqList;
import com.mtr.databoard.domain.components.ComponentStore;
import com.mtr.databoard.domain.middlewares.Parameter;
import com.mtr.databoard.domain.resources.ResourceStore;
import com.mtr.sdk.ExecuteResult;
import com.mtr.sdk.IMiddleware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

@Component
public class MiddlewareExecutor {

    private IScreentComponentRepository screentComponentRepository;
    private IScreenRepository screenRepository;
    private ComponentStore componentStore;
    private ResourceStore resourceStore;

    private static Map<String, Map<String, Map<String, String>>> parametersCache;




    public MiddlewareExecutor(@Autowired IScreentComponentRepository screentComponentRepository, @Autowired IScreenRepository screenRepository, @Autowired ComponentStore componentStore, @Autowired ResourceStore resourceStore) {
        this.screentComponentRepository = screentComponentRepository;
        this.screenRepository = screenRepository;
        this.componentStore = componentStore;
        this.resourceStore = resourceStore;
        if(parametersCache==null)
            parametersCache = new HashMap<>();
    }

    private void setOrUpdateParameter(String sessionId, String componentId, String name, String value) {
        if (!parametersCache.containsKey(sessionId))
            parametersCache.put(sessionId, new HashMap<>());

        var components = parametersCache.get(sessionId);
        if (!components.containsKey(componentId))
            components.put(componentId, new HashMap<>());

        var parameters = components.get(componentId);
        parameters.put(name, value);
    }


    public List<com.mtr.sdk.Parameter> getComponentParmeters(String sessionId, String componentId) {
        List<com.mtr.sdk.Parameter> sdkParameters = new ArrayList<>();
        if (!parametersCache.containsKey(sessionId))
            return sdkParameters;
        var component = parametersCache.get(sessionId);
        if (!component.containsKey(componentId))
            return sdkParameters;

        var parameters = component.get(componentId);
        parameters.forEach((name, value) -> {
            sdkParameters.add(new com.mtr.sdk.Parameter(name, value));
        });
        return sdkParameters;
    }


    /**
     * @param sessionId
     * @param screenId
     * @param sender
     */
    public void runBackgroundExecuteTask(String sessionId, UUID screenId, IMiddlewareMessageSender sender) {
        if (!screenRepository.existsById(screenId)) return;
        var screen = screenRepository.findById(screenId).get();
        var linqComponents = new LinqList<>(screen.getComponents());

        //缓存组件参数
        linqComponents.foreach(component -> {
            if (component.getMiddleware() == null) return;
            var middleware = component.getMiddleware();
            for (var parmeter : middleware.getParameters()) {
                setOrUpdateParameter(sessionId, component.getId().toString(), parmeter.getName(), parmeter.getValue());
            }

            if (middleware.isPush()&&middleware.getMiddlewareEntry()!=null&&middleware.getMiddlewareEntry().length()>0) {
                //TODO 启动定时线程，定时推送
                new Thread(new BackgroundExecuteTask(sessionId, screenId, component.getCode(), this, sender, middleware.getUpdateFrequency())).start();
            }
        });
    }


    /**
     * @param sessionId
     * @param screenId
     * @param componentCode
     * @param parameters
     * @return
     */
    public ExecuteResult execute(String sessionId, UUID screenId, String componentCode, List<Parameter> parameters) {
        if (!screenRepository.existsById(screenId)) return null;
        var screen = screenRepository.findById(screenId).get();
        var linqComponents = new LinqList<>(screen.getComponents());
        var component = linqComponents.firstOrDefault(c -> c.getView().getCode().equals(componentCode));
        if (component == null || component.getMiddleware() == null || component.getMiddleware().getMiddleware() == null)
            return null;

        var middleware = component.getMiddleware();

        var defaultParameters = middleware.getParameters();
        var newparameters = new LinqList<>(parameters);
        for (var parmeter : defaultParameters) {
            if (!parmeter.getIsPublic()) continue;
            var newParmeter = newparameters.firstOrDefault(p -> p.getName().equals(parmeter.getName()));
            if (newParmeter == null) continue;
            //parmeter.setValue(newParmeter.getValue());
            setOrUpdateParameter(sessionId, component.getId().toString(), newParmeter.getName(), newParmeter.getValue());
        }
        List<com.mtr.sdk.Parameter> sdkParameters = getComponentParmeters(sessionId, component.getId().toString());


        var middlewareFilePath = resourceStore.getFilePath("screen", screenId.toString() + "/middleware/" + middleware.getId().toString(), middleware.getMiddleware().getFileName());

        return execute(middlewareFilePath, middleware.getMiddlewareEntry(), sdkParameters);
    }


    /**
     * @param sessionId
     * @param screenId
     * @param componentCode
     * @return
     */
    public ExecuteResult execute(String sessionId, UUID screenId, String componentCode) {
        if (!screenRepository.existsById(screenId)) return null;
        var screen = screenRepository.findById(screenId).get();
        var linqComponents = new LinqList<>(screen.getComponents());
        var component = linqComponents.firstOrDefault(c -> c.getView().getCode().equals(componentCode));
        if (component == null || component.getMiddleware() == null || component.getMiddleware().getMiddleware() == null)
            return null;

        var middleware = component.getMiddleware();
        List<com.mtr.sdk.Parameter> sdkParameters = getComponentParmeters(sessionId, component.getId().toString());
        var middlewareFilePath = resourceStore.getFilePath("screen", screenId.toString() + "/middleware/" + middleware.getId().toString(), middleware.getMiddleware().getFileName());

        return execute(middlewareFilePath, middleware.getMiddlewareEntry(), sdkParameters);
    }


    /**
     * 执行中间件
     *
     * @param middlewareFilePath
     * @param middlewareEntry
     * @param sdkParameters
     * @return
     */
    private ExecuteResult execute(String middlewareFilePath, String middlewareEntry, List<com.mtr.sdk.Parameter> sdkParameters) {
        try {
            var url = new URL("file:" + middlewareFilePath);
            URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url}, Thread.currentThread().getContextClassLoader());
            Class<?> middlewareClass = null;
            if(middlewareEntry==null||middlewareEntry.length()==0) return null;
            middlewareClass = urlClassLoader.loadClass(middlewareEntry);
            IMiddleware iMiddleware = (IMiddleware) middlewareClass.getDeclaredConstructor().newInstance();


            var value = iMiddleware.Execute(sdkParameters);
            urlClassLoader.close();
            //写回数据
            return value;


        } catch (ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
